from flask import Flask, request, jsonify
from common.logger_config import setup_logger
from common.common import Response
from business.index_handler import IndexResponse, IndexDataResponse, check_index_param, create_vector_index, check_vector_index_exist, delete_vector_index, get_vector_index_info
from business.image_handler import calculate_md5, allowed_file, ImageUploadResponse, check_image_id_param, ImageSearchInnerResponse, ImageSearchResponse
from business.vector_handler import process_image, verify_image_category, delete_image, query_image_by_text
from business.face_handler import process_face
from PIL import Image
import requests
from urllib.parse import urlparse
from io import BytesIO
import os
import time
import uuid
import logging
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import threading

app = Flask(__name__)

logger = setup_logger(__name__)  # 获取日志对象

# 禁用 werkzeug 日志
logging.getLogger('werkzeug').disabled = True


def load_config():
    config_file = ""

    # 获取环境变量来决定加载哪个配置文件
    env = os.getenv('PROJECT_ENV') or "dev"
    logger.info(f"PROJECT_ENV: {env}")

    if env == 'dev':
        config_file = "config/config_dev.py"
    if env == 'qa':
        config_file = "config_qa.py"
    elif env == 'test':
        config_file = "config/config_test.py"
    elif env == 'prod':
        config_file = "config_prod.py"

    app.config.from_pyfile(config_file)
    logger.info(f"Loaded {env} configuration")


@app.route('/v1/image-search/index', methods=['POST'])  # 批量创建索引
def create_index():
    data = request.get_json()
    param = data.get("indexName")
    response, status_code = check_index_param(param)
    if response.json.get("status") != 200:
        logger.info("【CreateIndex】create index param is error.")
        return response, status_code
    data_result = []
    application_name = "nas"
    for item in param:
        try:
            # 1、同一应用下，不能重复
            index_exist_status = check_vector_index_exist(application_name, item)
            if index_exist_status:
                logger.info(f"【CreateIndex】the specified index is existed. application_name = {application_name}, index_name = {item}")
                data_result.append(IndexResponse(item, "indexName duplicate").to_dict())
                continue
            create_result = create_vector_index(application_name, item)
            logger.info(f"【CreateIndex】create vector index finish. application_name = {application_name}, index_name = {item}, result = {create_result}")
            data_result.append(IndexResponse(item, "success" if create_result else "failure").to_dict())
        except Exception as e:
            logger.error(f"【CreateIndex】create index error. application_name = {application_name}, index_name = {item}, error = {e}")
            data_result.append(IndexResponse(item, "create error").to_dict())
    return jsonify(Response(200, "", None, data_result).to_dict()), 200


@app.route('/v1/image-search/index', methods=['DELETE'])  # 批量删除索引
def delete_index():
    data = request.get_json()
    param = data.get("indexName")
    response, status_code = check_index_param(param)
    if response.json.get("status") != 200:
        logger.info("【DeleteIndex】delete index param is error.")
        return response, status_code
    data_result = []
    application_name = "nas"
    for item in param:
        try:
            index_exist_status = check_vector_index_exist(application_name, item)
            if not index_exist_status:  # 索引不存在
                logger.info(f"【DeleteIndex】the specified index is not existed. application_name = {application_name}, index_name = {item}")
                data_result.append(IndexResponse(item, "index is not existed").to_dict())
                continue
            delete_result = delete_vector_index(application_name, item)
            logger.info(f"【DeleteIndex】delete vector index finish. application_name = {application_name}, index_name = {item}, result = {delete_result}")
            data_result.append(IndexResponse(item, "success" if delete_result else "failure").to_dict())
        except Exception as e:
            logger.error(f"【DeleteIndex】delete index error. application_name = {application_name}, index_name = {item}, error = {e}")
            data_result.append(IndexResponse(item, "create error").to_dict())
    return jsonify(Response(200, "", None, data_result).to_dict()), 200


@app.route('/v1/image-search/index/query', methods=['POST'])
def get_index():
    data = request.get_json()
    param = data.get("indexName")
    response, status_code = check_index_param(param)
    if response.json.get("status") != 200:
        logger.info("【SearchIndex】search index param is error.")
        return response, status_code
    data_result = []
    application_name = "nas"
    for item in param:
        try:
            index_exist_status = check_vector_index_exist(application_name, item)
            if not index_exist_status:  # 索引不存在
                logger.info(f"【SearchIndex】the specified index is not existed. application_name = {application_name}, index_name = {item}")
                data_result.append(IndexDataResponse(item, "index is not existed", -1).to_dict())
                continue
            index_info = get_vector_index_info(application_name, item)
            logger.info(f"【SearchIndex】search vector index finish. application_name = {application_name}, index_name = {item}, result = {index_info.to_json()}")
            data_result.append(IndexDataResponse(item, "success", getattr(index_info, "count")).to_dict())
        except Exception as e:
            logger.error(f"【SearchIndex】search index error. application_name = {application_name}, index_name = {item}, error = {e}")
            data_result.append(IndexDataResponse(item, "error", -1).to_dict())
    return jsonify(Response(200, "", None, data_result).to_dict()), 200


# 最大文件大小设置（5MB）
MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB


# @app.route('/openapi/v1/image-search/image', methods=['POST'])
# def upload_file():
#     data = request.get_json()
#     image_url = data.get("imageUrl")
#     if not image_url:
#         return jsonify(Response(20001, "param failure", None, "image url is blank").to_dict()), 200
#     logger.info("【UploadImage】upload file url start. image_url = %s", image_url)
#
#     index_name = data.get("indexName")
#     if not index_name:
#         return jsonify(Response(20001, "param failure", None, "indexName is blank").to_dict()), 200
#
#     # 提取文件名和后缀
#     url_path = urlparse(image_url).path
#     logger.info("【UploadImage】file origin url = %s", url_path)
#
#     origin_file_name = os.path.basename(url_path)  # 例如 'melo.jpg'
#     logger.info("【UploadImage】file_name = %s", origin_file_name)
#
#     allow_result = allowed_file(origin_file_name)
#     if not allow_result:
#         return jsonify(Response(20001, "param failure", None, "file type is not allowed").to_dict()), 200
#
#     # 发送 GET 请求获取图片
#     response = requests.get(image_url)
#     if response.status_code != 200:
#         return jsonify(Response(20001, "param failure", None, "download image from url error.").to_dict()), 200
#
#     file_size = len(response.content)
#     if not file_size or file_size > MAX_FILE_SIZE:
#         logger.info(f"【UploadImage】file size is error. file_size = {file_size}")
#         return jsonify(Response(20001, "param failure", None, "file size is None or exceeds 5MB").to_dict()), 200
#
#     file = Image.open(BytesIO(response.content))
#     if not file:
#         return jsonify(Response(20001, "param failure", None, "download image success. file is None.").to_dict()), 200
#
#     # 保存文件
#     file_name_without_extension, file_extension = os.path.splitext(origin_file_name)  # ('melon', '.jpg')
#     # image_id = file_name_without_extension + "-" + str(int(time.time() * 1_000_000_000))  # 图片ID
#     # image_id = file_name_without_extension + "-" + str(uuid.uuid4())  # 图片ID
#     image_id = str(uuid.uuid4())  # 图片ID
#
#     image_name = image_id + file_extension
#     image_save_url = os.path.join(os.getcwd(), app.config['UPLOAD_FOLDER'])
#
#     if not os.path.exists(image_save_url):
#         logger.info("【UploadImage】create upload dir finish. dir_path = %s", image_save_url)
#         os.makedirs(image_save_url)
#
#     save_file_path = os.path.join(image_save_url, image_name)
#     file.save(save_file_path)
#     logger.info("【UploadImage】save file finish. file_path = %s", save_file_path)
#
#     file_md5 = calculate_md5(response)
#     logger.info("【UploadImage】upload file url finish. file_url = %s, md5 = %s" % (image_url, file_md5))
#
#     # 图片向量化
#     application_name = "nas"
#     index_exist_status = check_vector_index_exist(application_name, index_name)
#     logger.info(f"【UploadImage】check index exist status. application_name = {application_name}, index_name = {index_name}, result = {index_exist_status}")
#     if not index_exist_status:  # 索引不存在
#         #  创建索引
#         create_result = create_vector_index(application_name, index_name)
#         if not create_result:
#             logger.info(f"【CreateIndex】create vector index error. application_name = {application_name}, index_name = {index_name}")
#             return jsonify(Response(20001, "param failure", None, "index is not exist, create index error").to_dict()), 200
#
#     process_result = process_image(image_save_url, image_name, application_name, index_name, image_id)
#     logger.info("【UploadImage】process image finish. result = %s", process_result)
#
#     # 获取图片标签
#     category_result = verify_image_category(image_save_url, image_name)
#     logger.info("【UploadImage】verify image category finish. result = %s", category_result)
#
#     face = data.get("face")
#     if not face or face == "false":
#         return jsonify(Response(200, "", None, ImageUploadResponse(index_name, image_id, category_result, [], file_md5).to_dict()).to_dict()), 200
#
#     # 处理人脸信息
#     face_threshold = data.get("face_threshold") or 0.99
#     logger.info("【UploadImage】face threshold = %s", face_threshold)
#
#     face_result = process_face(image_save_url, image_name, application_name, index_name, face_threshold, image_id)
#     logger.info("【UploadImage】process face finish. result_size = %s", len(face_result))
#
#     return jsonify(Response(200, "", None, ImageUploadResponse(index_name, image_id, category_result, face_result, file_md5).to_dict()).to_dict()), 200


# 删除图片
@app.route('/v1/image-search/image', methods=['DELETE'])
def delete_file():
    data = request.get_json()
    index_name = data.get("indexName")
    if not index_name:
        return jsonify(Response(20001, "param failure", None, "indexName is blank").to_dict()), 200

    image_ids = data.get("imageIds")
    check_image_ids_result, status_code = check_image_id_param(image_ids)
    if check_image_ids_result.json.get("status") != 200:
        logger.info("【DeleteImage】param is error.")
        return check_image_ids_result, status_code

    success_delete_image_ids = []

    application_name = "nas"
    for image_id in image_ids:
        delete_image_result = delete_image(application_name, index_name, image_id)
        logger.info("【DeleteImage】delete image finish. image_id = %s, result = %s", image_id, delete_image_result)

        if delete_image_result and len(delete_image_result) != 0 and delete_image_result[0] != 0:
            success_delete_image_ids.append(image_id)

    return jsonify(Response(200, "", None, success_delete_image_ids).to_dict()), 200


# 图片搜索
@app.route('/v1/image-search/search', methods=['POST'])
def image_search():
    data = request.get_json()
    page_num = data.get("pageNum")
    if not page_num or int(page_num) < 1:
        return jsonify(Response(20001, "param failure", None, "pageNum is blank or less than 1").to_dict()), 200

    page_num = int(page_num) - 1

    page_size = data.get("pageSize")
    if not page_size or int(page_size) <= 0 or int(page_size) > 500:
        return jsonify(Response(20001, "param failure", None, "pageSize is blank or less than 1 or large than 500").to_dict()), 200

    index_name = data.get("indexName")
    if not index_name:
        return jsonify(Response(20001, "param failure", None, "indexName is blank").to_dict()), 200

    application_name = "nas"
    score = data.get("score") or 0.7
    text = data.get("text")
    if not text:
        return jsonify(Response(20001, "param failure", None, "text is blank").to_dict()), 200

    query_result = query_image_by_text(application_name, index_name, score, page_num, page_size, text)
    logger.info("【ImageSearch】query image by text finish. text = %s, result = %s", text, query_result)

    inner_response_result = []
    for document in query_result:
        id = document["id"]
        image_id = id[id.rfind(":") + 1:]

        score = document["vector_score"]

        inner_response_result.append(ImageSearchInnerResponse(image_id, score))

    response = ImageSearchResponse(inner_response_result, len(query_result)).to_dict()
    return jsonify(Response(200, "", None, response).to_dict()), 200


@app.route('/v1/image-search/image', methods=['POST'])
def upload_image_v2():
    start = time.time() * 1000
    data = request.get_json()
    image_url = data.get("imageUrl")
    if not image_url:
        return jsonify(Response(20001, "param failure", None, "image url is blank").to_dict()), 200
    logger.info("【UploadImage】upload file url start. image_url = %s", image_url)

    index_name = data.get("indexName")
    if not index_name:
        return jsonify(Response(20001, "param failure", None, "indexName is blank").to_dict()), 200
    try:
        # 提取文件名和后缀
        url_path = urlparse(image_url).path
        logger.info("【UploadImage】file origin url = %s", url_path)

        origin_file_name = os.path.basename(url_path)  # 例如 'melo.jpg'
        logger.info("【UploadImage】file_name = %s", origin_file_name)

        allow_result = allowed_file(origin_file_name)
        if not allow_result:
            return jsonify(Response(20001, "param failure", None, "file type is not allowed").to_dict()), 200

        response = requests.get(image_url)
        if response.status_code != 200:
            return jsonify(Response(20001, "param failure", None, "download image from url error.").to_dict()), 200

        file_size = len(response.content)
        if not file_size or file_size > MAX_FILE_SIZE:
            logger.info(f"【UploadImage】file size is error. file_size = {file_size}")
            return jsonify(Response(20001, "param failure", None, "image size is None or exceeds 5MB").to_dict()), 200

        image = Image.open(BytesIO(response.content))
        if not image:
            return jsonify(Response(20001, "param failure", None, "download image success. file is None.").to_dict()), 200

        file_name_without_extension, file_extension = os.path.splitext(origin_file_name)  # ('melon', '.jpg')
        image_id = str(uuid.uuid4())  # 图片ID

        image_md5 = calculate_md5(response)
        logger.info("【UploadImage】calculate image md5 finish. image_url = %s, md5 = %s" % (image_url, image_md5))

        # 图片向量化
        application_name = "nas"
        index_exist_status = check_vector_index_exist(application_name, index_name)
        if not index_exist_status:  # 索引不存在
            #  创建索引
            create_result = create_vector_index(application_name, index_name)
            if not create_result:
                logger.info(f"【UploadImage】index is not exist. create vector index error. application_name = {application_name}, index_name = {index_name}")
                return jsonify(Response(20001, "param failure", None, "index is not exist, create index error").to_dict()), 200
        logger.info("【UploadImageCost】check and generate base info finish. cost time = %s.", time.time() * 1000 - start)

        process_image_start = time.time() * 1000
        process_result = process_image(image, application_name, index_name, image_id)
        logger.info("【UploadImage】process image finish. result = %s", process_result)
        logger.info("【UploadImageCost】process image finish. cost time = %s.", time.time() * 1000 - process_image_start)

        # 获取图片标签
        category_image_start = time.time() * 1000
        category_result = verify_image_category(image)
        logger.info("【UploadImage】verify image category finish. result = %s", category_result)
        logger.info("【UploadImageCost】category image finish. cost time = %s.", time.time() * 1000 - category_image_start)

        face = data.get("face")
        if not face or face == "false":
            return jsonify(Response(200, "", None, ImageUploadResponse(index_name, image_id, category_result, [], image_md5).to_dict()).to_dict()), 200

        # 处理人脸信息
        face_threshold = data.get("face_threshold") or 0.99
        logger.info("【UploadImage】face threshold = %s", face_threshold)

        process_face_start_time = time.time() * 1000
        face_result = process_face(image, application_name, index_name, face_threshold, image_id)
        logger.info("【UploadImage】process face finish. result_size = %s", len(face_result))
        logger.info("【UploadImageCost】process face finish. cost time = %s.", time.time() * 1000 - process_face_start_time)

        return jsonify(Response(200, "", None, ImageUploadResponse(index_name, image_id, category_result, face_result, image_md5).to_dict()).to_dict()), 200

    except Exception as e:
        logger.error(f"【UploadImage】upload image error. image_url = {image_url}, index_name = {index_name}, error = {e}")
        return jsonify(Response(20001, "error", None, "upload image error.").to_dict()), 200


def start_file_watcher():
    event_handler = FileSystemEventHandler()

    # 定义文件监控逻辑
    def on_modified(event):
        print(f"Config file {event.src_path} modified start.")
        if event.src_path.endswith('config_dev.py') or event.src_path.endswith('config_qa.py') or event.src_path.endswith('config_test.py') or event.src_path.endswith('config_prod.py'):
            logger.info(f"Config file {event.src_path} modified, reloading...")
            load_config()
            logger.info(f"Config file {event.src_path} modified, reload finish.")

    event_handler.on_modified = on_modified
    observer = Observer()
    observer.schedule(event_handler, '/data/script/web/config', recursive=False)
    observer.start()

    try:
        while True:
            time.sleep(5)
    except KeyboardInterrupt:
        observer.stop()

    observer.join()


# 启动 Web 服务
if __name__ == '__main__':
    load_config()

    # 使用线程启动文件监控器，避免阻塞
    file_watcher_thread = threading.Thread(target=start_file_watcher)
    file_watcher_thread.daemon = True
    file_watcher_thread.start()

    app.run(host='0.0.0.0', port=5001)

